package cnisetup

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/f5devcentral/f5-bigip-rest-go/utils"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

func (cbc *CalicoBGPContext) Setup() error {
	slog := utils.LogFromContext(cbc)
	calicoset := newDynamicClient(cbc.Kube.Config)

	group, version := "crd.projectcalico.org", "v1"
	applyOps := metav1.ApplyOptions{FieldManager: strings.Join([]string{group, version}, "/")}

	gvrBGPConf := schema.GroupVersionResource{
		Group:    group,
		Version:  version,
		Resource: "bgpconfigurations",
	}

	gvrBGPPr := schema.GroupVersionResource{
		Group:    group,
		Version:  version,
		Resource: "bgppeers",
	}

	for _, bgp := range cbc.BGPs {
		remoteAS, err1 := strconv.ParseInt(bgp.RemoteAS, 10, 0)
		localAS, err2 := strconv.ParseInt(bgp.LocalAS, 10, 0)
		if err1 != nil || err2 != nil {
			return fmt.Errorf("failed to parse as number from input: %v %v", err1, err2)
		}
		bgpConfName := bgp.Name
		confyaml := unstructured.Unstructured{
			Object: map[string]interface{}{
				"apiVersion": strings.Join([]string{group, version}, "/"),
				"kind":       "BGPConfiguration",
				"metadata":   map[string]interface{}{"name": bgpConfName},
				"spec": map[string]interface{}{
					"asNumber":              remoteAS,
					"logSeverityScreen":     "Info",
					"nodeToNodeMeshEnabled": true,
				},
			},
		}
		applyedConf, err := calicoset.Resource(gvrBGPConf).Apply(context.TODO(), bgpConfName, &confyaml, applyOps)
		if err != nil {
			return err
		} else {
			slog.Infof("successfully applied BGPConfiguration: %s", applyedConf.GetName())
		}

		for _, prIP := range cbc.Kube.PeerIPs {
			bgpPeerName := fmt.Sprintf("bgppeer-%s-%s", bgp.Name, prIP)
			pryaml := unstructured.Unstructured{
				Object: map[string]interface{}{
					"apiVersion": strings.Join([]string{group, version}, "/"),
					"kind":       "BGPPeer",
					"metadata": map[string]interface{}{
						"name": bgpPeerName,
					},

					"spec": map[string]interface{}{
						"asNumber": localAS,
						"peerIP":   prIP,
					},
				},
			}

			appliedPr, err := calicoset.Resource(gvrBGPPr).Apply(context.TODO(), bgpPeerName, &pryaml, applyOps)
			if err != nil {
				return err
			} else {
				slog.Infof("successfully applied BGPPeer: %s", appliedPr.GetName())
			}
		}
	}

	return nil
}

func (cbc *CalicoBGPContext) Teardown() error {
	slog := utils.LogFromContext(cbc)
	calicoset := newDynamicClient(cbc.Kube.Config)

	group, version := "crd.projectcalico.org", "v1"
	deleteOps := metav1.DeleteOptions{}

	gvrBGPConf := schema.GroupVersionResource{
		Group:    group,
		Version:  version,
		Resource: "bgpconfigurations",
	}

	gvrBGPPr := schema.GroupVersionResource{
		Group:    group,
		Version:  version,
		Resource: "bgppeers",
	}

	for _, bgp := range cbc.BGPs {
		bgpConfName := bgp.Name
		if err := calicoset.Resource(gvrBGPConf).Delete(context.TODO(), bgpConfName, deleteOps); err != nil {
			if strings.Contains(err.Error(), "the server could not find the requested resource") {
				slog.Warnf("resource type BGPConfiguration not exist")
				continue
			} else {
				return err
			}
		} else {
			slog.Infof("successfully deleted BGPConfiguration: %s", bgpConfName)
		}
	}

	for _, bgp := range cbc.BGPs {
		for _, prIP := range cbc.Kube.PeerIPs {
			bgpPeerName := fmt.Sprintf("bgppeer-%s-%s", bgp.Name, prIP)
			if err := calicoset.Resource(gvrBGPPr).Delete(context.TODO(), bgpPeerName, deleteOps); err != nil {
				if strings.Contains(err.Error(), "the server could not find the requested resource") {
					slog.Warnf("resource type BGPPeer not exist")
					continue
				} else {
					return err
				}
			} else {
				slog.Infof("successfully deleted BGPPeer: %s", bgpPeerName)
			}
		}
	}
	return nil
}

func (cbc *CalicoBGPContext) nodeConfigs() (map[string]interface{}, error) {
	slog := utils.LogFromContext(cbc)
	clientset := newKubeClient(cbc.Kube.Config)

	nodeList, err := clientset.CoreV1().Nodes().List(cbc, metav1.ListOptions{})
	if err != nil {
		slog.Errorf("failed to list nodes: %s", err.Error())
		return nil, err
	}

	cfgs := map[string]interface{}{}
	nIpAddresses := allNodeIpAddrs(cbc, nodeList)
	for _, bgp := range cbc.BGPs {
		ccfgs := parseNeighsFrom(bgp.Name, bgp.RemoteAS, nIpAddresses)
		for k, v := range ccfgs {
			cfgs[k] = v
		}
	}
	return cfgs, nil
}
